(function(djp){
  var
  boxCSSText = ['position:absolute;padding:0;margin:0;z-index:98;','top:0;left:0;'],
  doc = document,
  rootNode = doc.documentElement,
  /* Показывает блок слева от вызвавшего его элемента или если нет места справа.
   * верхнаяя граница блока выравнена по центру от вызываемого элемента.
   */
  currentRecalc = function(target, boxNode){
    var
    offset = forRecalc(target, boxNode);
    offset.top += target.node.offsetHeight >> 1;
    boxNode.node.style.cssText = boxCSSText[0] + [
      'top:', offset.top, 'px;',
      'left:', offset.left, 'px;',
      'width:', offset.width, 'px;'
    ].join('');
  };
  /**
   * отображает блок поверх страницы в заданной позиции.
   * @param {DJProxy=} target эелемент относительно которого будет позиционироваться блок
   * @param {{
   *   openCallback: function(boxNode, recalc, target),
   *   closeCallback: function(boxNode)|undefined=,
   *   closeClass: string|undefined=,
   *   pos: {x:integer, y:integer}|undefined=
   * }} options настройки
   * @return {function()} close
   */
  djp.openBox = function(target, options){
    var
    timer,
    boxNode,
    onResizeToggler,
    onCloseToggler,
    recalc = options.recalc || currentRecalc;
    options = options || {};
    options.closeClass = options.closeClass || 'closeButton';
    function fClose (){
      var tmp;
      if(boxNode){
        onCloseToggler();
        onResizeToggler();
        options.closeCallback && (options.closeCallback(boxNode));
        (tmp = boxNode.node.parentNode).parentNode.removeChild(tmp);
        boxNode = null;
      }
    }
    if(!boxNode){
      boxNode = djp(doc.body).append(
        '<div style="margin:-1px 0 0;text-align:left;">' +
        '<div style="' + boxCSSText.join('') + '"></div>'
      ).child();
      options.openCallback(boxNode, recalc, target);
      recalc(target, boxNode);
      onCloseToggler = djp(doc).attach(
        'keyup ' + (rootNode['uniqueID'] ? 'mouseup' : 'mousedown'),
        function (oTargetDJProxy, oEvent){
          if(
            oEvent.$type == 'keyup' &&
            oEvent.$keyCode == 27 ||
            !boxNode.contains(oTargetDJProxy)
          ){
            fClose();
          } else if(
            boxNode.contains(
              oTargetDJProxy.ancestor().hasClass(options.closeClass)
            ) && (
              oEvent.$type != 'keyup' ||
              oEvent.$keyCode == 32 ||
              oEvent.$keyCode == 13
            )
          ){
            fClose();
            oEvent.prevent()
          }
        }
      );
      onResizeToggler = djp(window).attach(
        'resize scroll',
        function(){
          typeof timer != 'undefined' && clearTimeout(timer);
          timer = setTimeout(
            function(){
              boxNode && recalc(target, boxNode)
            },
            5
          )
        }
      );
    }
    return fClose
  };
  function forRecalc(target, boxNode){
    var offset = target.offset(), isToLeft = true;
    boxNode.node.style.cssText = boxCSSText.join('') + 'width:';
    offset.width = boxNode.node.offsetWidth
    offset.left -= offset.left < offset.width ? (
      isToLeft=!isToLeft,
      -target.node.offsetWidth
    ) :
    offset.width;
    boxNode.ancestor().more().toggleClass('toLeft', isToLeft);
    return offset
  }
  /* Показывает блок поцентру окна просмотра */
  djp.openBox.centre = function(target, options){
    options.recalc = function(target, boxNode){
      var k, o = {'Width':'Left','Height':'Top'}, t, a = [];
      boxNode.node.style.cssText = boxCSSText.join('');
      for (k in o){
        a[a.length] = o[k].toLowerCase() + ':' + (
          (
            t = (
              typeof window['inner'+k] == 'undefined' ?
              rootNode['client'+k] || doc.body['client'+k] :
              window['inner'+k]
            ) - boxNode.node['offset'+k]
          ) < 0 ?
            0 :
            ((t>>1) + Math.max(doc.body[t='scroll'+o[k]], rootNode[t])) + 'px'
        ) + 'px;'
      }
      boxNode.node.style.cssText = boxCSSText[0] + a.join('');
    };
    return djp.openBox(target, options)
  };
  /* Показывает блок вырвненным по нижней границе с вызвавшим его элементом и слева.
   * Если сверху нет места чтобы блок влез целиком,
   * выравниваются верхняя граница блока с нижней границей элемента.
   * Если нет места слева , блок будет справа от вызвавшего его элемента.
   */
  djp.openBox.alignLeftInside = function(target, options){
    currentRecalc = function(target, boxNode){
      var
      isBelow = true,
      offset = forRecalc(target, boxNode),
      tHeight = target.node.offsetHeight,
      bHeight = boxNode.node.offsetHeight;
      offset.top -= offset.top -
      Math.max(doc.body.scrollTop, rootNode.scrollTop) < bHeight - tHeight ?
      -tHeight : (
        isBelow = !isBelow,
        -tHeight + bHeight
      );
      boxNode.toggleClass('below', isBelow);
      boxNode.node.style.cssText = boxCSSText[0] + [
        'top:', offset.top, 'px;',
        'left:', offset.left, 'px;',
        'width:', offset.width, 'px;'
      ].join('');
    };
    return djp.openBox(target, options)
  };
  /* 3 Показывает блок слева от вызвавшего его элемента или если нет места справа.
   * верхнаяя граница блока выровнена по центру от вызываемого элемента.
   */
  djp.openBox.centreAside = function(target, options){
    currentRecalc = function(target, boxNode){
      var
      offset = forRecalc(target, boxNode);
      offset.top += (target.node.offsetHeight - boxNode.node.offsetHeight) >> 1;
      boxNode.node.style.cssText = boxCSSText[0] + [
        'top:', offset.top, 'px;',
        'left:', offset.left, 'px;',
        'width:', offset.width, 'px;'
      ].join('');
    };
    return djp.openBox(target, options)
  };
  /* Показывает блок над или если нет места то под элементом, вызвавшем его.
   */
  djp.openBox.overOrBelow = function(target, options){
    currentRecalc = function(target, boxNode){
      var
      offset = target.offset(),
      isBelow = true,
      tHeight,
      bHeight,
      visibleWidth = (typeof window['innerWidth'] == 'undefined' ?
      rootNode['clientWidth'] || doc.body['clientWidth'] :
      window['innerWidth'])- 40,
      tmp,
      tmp2;
      boxNode.node.style.cssText = [
        boxCSSText[0],
        'left:0;top:0;',
        'px;width:;'
      ].join('');
      tmp = visibleWidth - boxNode.node.offsetWidth;
      offset.width = boxNode.node.offsetWidth;
      if (tmp < 0){
        boxNode.node.style.width = (offset.width += tmp) + 'px'
      }
      tHeight = target.node.offsetHeight;
      bHeight = boxNode.node.offsetHeight;
      offset.top -= offset.top -
      Math.max(doc.body.scrollTop, rootNode.scrollTop) < bHeight ?
      -tHeight : (
        isBelow = !isBelow,
        bHeight
      );
      if(options.pos){
        tmp = Math.max(doc.body.scrollLeft, rootNode.scrollLeft);
        tmp2 = options.pos.x + offset.width;
        offset.left = tmp2 - tmp > visibleWidth ?
        tmp + visibleWidth - offset.width :
        options.pos.x
      }
      boxNode.toggleClass('below', isBelow);
      boxNode.node.style.cssText = boxCSSText[0] + [
        'top:', offset.top, 'px;',
        'left:', offset.left, 'px;',
        'width:', offset.width, 'px;'
      ].join('');
    };
    return djp.openBox(target, options)
  };
})(djp);